#include "../include/kasm.h"
#include "../include/defs.h"
#include "../include/kernel.h"
#include "../include/shell.h"
#include "../include/io.h"
#include "../include/keyboard.h"
#include "../include/fd.h"
#include "../include/video.h"
#include "../include/malloc.h"
#include "../include/arqDefs.h"
#include "../include/sched.h"
#include "../include/string.h"
#include "../include/libc.h"

#define MAX_ARG 15
#define LEDS(capslock, numlock, scrollock)  (capslock) * 4 + (numlock) * 2 + (scrollock);

static void SetupPaging(void);


/*Globales*/
DESCR_INT idt[0x81];        /* IDT de 80 entradas */
IDTR idtr;                  /* IDTR */
fileDescriptorManager FDM;  /*FDM*/
screenManager screen;       /*Pantalla*/
PROCESS procesos[MAX_PROC]; /*Procesos*/
TTY tty[TTY_CANT];          /*TTYs*/
extern int nextfree;

PROCESS idle;


/* Page Directory */
unsigned long *page_directory = (unsigned long *) 0x9C000;

/* Page Table */
unsigned long *page_table = (unsigned long *) 0x9D000;
unsigned long *page_table2 = (unsigned long *) 0x9E000;
int page_pid[CANT_PAGES];

/** @ man stdout:
** On  program  startup, the integer file descriptors associated
** with the streams stdin, stdout, and stderr are 0, 1, and 2, respectively.
*/
int stdin;
int stdout;
int stderr;

/* Externas de keyboard */
extern tipoFlags flags[TTY_CANT];
extern int layout;


// Variables globales
int CurrentTTY = 0;
int nextPID = 1;
int CurrentPID = 0;
unsigned short *backupTerm;

/* Interrupcion 9 para el teclado */

void int_09()
{
    _Cli();
    keyboard_handler();
    _Sti();
    return;
}

/**********************************************
int kmain(void)
Punto de entrada de codigo C.
*************************************************/

int
kmain(void)
{
    setup_IDT_entry (&idt[0x80], 0x08, (dword)&_int_80_hand, ACS_INT, 0);
    setup_IDT_entry (&idt[0x08], 0x08, (dword)&_int_08_hand, ACS_INT, 0);
    /* CARGA DE IDT CON LA RUTINA DE ATENCION 09h Keyboard  */
    setup_IDT_entry (&idt[0x09], 0x08, (dword)&_int_09_hand, ACS_INT, 0);

    // stack exception
    setup_IDT_entry (&idt[0x0C], 0x08, (dword)&ssf_hand, ACS_INT, 0);
    // general protection
    setup_IDT_entry (&idt[0x0D], 0x08, (dword)&gpf_hand, ACS_INT, 0);
    // segment not present
    setup_IDT_entry (&idt[0x0B], 0x08, (dword)&snp_hand, ACS_INT, 0);
    // page fault
    setup_IDT_entry (&idt[0x0E], 0x08, (dword)&pf_hand, ACS_INT, 0);


    /* Carga de IDTR    */
    idtr.base = 0;
    idtr.base +=(dword) &idt;
    idtr.limit = sizeof(idt)-1;

    _lidt (&idtr);

    _Cli();
    SetupPaging();


    /* Habilito interrupcion de timer tick*/
    _mascaraPIC1(0xFC);
    _mascaraPIC2(0xFF);

    /*Inicialización de fds*/
    initFDM();

    /*No se usa funcion de setteo */
    stdin  = fdNew( NULL, NULL, dequeueBuffer);
    stdout = fdNew(NULL, videoSetByte, NULL);

    /*TODO: Por ahora stderr va a pantalla*/
    stderr = stdout;


    SetupScheduler();
    init_ttys();

    CreateProcessAt("Shell 1", shell, 0, 1, (char **)1, 0x400, 2, 1);
    CreateProcessAt("Shell 2", shell, 1, 1, (char **)2, 0x400, 2, 1);
    CreateProcessAt("Shell 3", shell, 2, 1, (char **)3, 0x400, 2, 1);
    CreateProcessAt("Shell 4", shell, 3, 1, (char **)4, 0x400, 2, 1);
    CreateProcessAt("Shell 5", shell, 4, 1, (char **)5, 0x400, 2, 1);
    CreateProcessAt("Shell 6", shell, 5, 1, (char **)6, 0x400, 2, 1);
    CreateProcessAt("Shell 7", shell, 6, 1, (char **)7, 0x400, 2, 1);
    CreateProcessAt("Shell 8", shell, 7, 1, (char **)8, 0x400, 2, 1);

    _Sti();

    while(1)
    {
//        shell(1, 1);
//        shell(1, 2);
    }

    return 1;
}

PROCESS *GetProcessByPID(int pid)
{
    int i;

    if (pid == 0)
    {
        return &idle;
    }

    for(i=0;i<MAX_PROC;i++)
    {
        if (procesos[i].pid== pid)
        {
            return &procesos[i];
        }
    }

    return 0;
}

int GetTTY(void)
{
    PROCESS *temp;
    temp = GetProcessByPID(CurrentPID);

    return temp->tty;
}

int GetPID(void)
{
    return nextPID++;
}

void init_ttys(void)
{
    int i;
    unsigned short leds;

    Init_Flags();
    set_key_map( ENGLISH_KB);
    for( i=0 ; i < TTY_CANT ; i++)
    {
        tty[i].screen.address  = (unsigned short *)CallocVid(VIDEO_WIDTH * VIDEO_HEIGTH * 2);
        tty[i].screen.offset   = VIDEO_OFFSET;
        tty[i].screen.heigth   = VIDEO_HEIGTH;
        tty[i].screen.width    = VIDEO_WIDTH;
        tty[i].screen.bGColor  = BLACK;
        tty[i].screen.textColor= LGREY;
        tty[i].buffer.first = 0;
        tty[i].buffer.last = 0;
        tty[i].buffer.cant = 0;
        leds = (unsigned short)LEDS(flags[i].caps_lock, flags[i].num_lock, flags[i].scroll_lock);

    }

    setLights(&leds);
    backupTerm = tty[0].screen.address;
    tty[0].screen.address = (unsigned short *) VIDEO_ADDRESS;
}



//Funcion que arma los stack frames de cada proceso
int LoadStackFrame(int(*process)(int,char**),int argc,char** argv,
                                    int bottom, void(*cleaner)())
{
    STACK_FRAME* frame= (STACK_FRAME*)(bottom-sizeof(STACK_FRAME));
    frame->EBP=0;
    frame->EIP=(int)process;
    frame->CS=0x08;
    frame->EFLAGS=0;
    frame->retaddr=cleaner;
    frame->argc=argc;
    frame->argv=argv;
    return (int)frame;
}

//Funcion que crea procesos
void CreateProcessAt(char* name, int (*process)(int,char**),int tty, int argc,
                    char** argv, int stacklength, int priority, int isFront)
{
    int i, j;
    void* stack;
    int flag = 0;

    /*Busca proceso libre*/
    for( i=0 ; i < MAX_PROC ; i++)
    {
        if(procesos[i].free==1)
            break;
    }

    procesos[i].pid=GetPID();
    procesos[i].foreground=isFront;
    procesos[i].priority=priority;
    memcpy(procesos[i].name, name, strlen(name)+1);
    procesos[i].sleep=0;
    procesos[i].blocked=0;
    procesos[i].tty=tty;
    procesos[i].lastCalled=0;
    procesos[i].stacksize=stacklength;
    procesos[i].stackpages=1;
    procesos[i].free=0;
    procesos[i].parent = CurrentPID;

    /* Le asigno las paginas */
    for(j=0; j < CANT_PAGES  && !flag; j++)
    {
        if (page_pid[j] == -1 && page_pid[j+1] == -1)
        {
            procesos[i].heap_page = j;
            procesos[i].stack_page = j + 1;

//            page_pid[j] = procesos[i].pid;
            page_pid[j] = -(procesos[i].pid * 100);

            page_pid[j+1] = procesos[i].pid;
            flag = 1;
        }
    }


	procesos[i].next_free = 4 * 1024 * 1024 + 4096*procesos[i].stack_page;
	/* Reservo memoria para el stack del proceso */

	nextfree = procesos[i].next_free;

	procesos[i].next_free = 4 * 1024 * 1024 + 4096*procesos[i].heap_page;
	/* Habilito las paginas para poder hacer el malloc del stack */
	page_table2[procesos[i].heap_page] = page_table2[procesos[i].heap_page] | 3;
	page_table2[procesos[i].stack_page] = page_table2[procesos[i].stack_page] | 3;
	stack = Malloc (stacklength);

    procesos[i].ESP=LoadStackFrame(process,argc,argv,
                (int)(stack+stacklength-1), Cleaner);

	procesos[i].stackstart = (int) stack;

	/* Una vez hecho el malloc, tengo que deshabilitarlas */
//	page_table2[procesos[i].heap_page] &= 0xFFFFFFFE;
	page_table2[procesos[i].stack_page] &= 0xFFFFFFFE;


    if  ( isFront && CurrentPID >= 1 )
    {
        PROCESS *proc = GetProcessByPID(CurrentPID);
        proc->blocked=2;
        procesos[i].parent=CurrentPID;
    }

    return;
}

size_t
__read(int fd, const void *buf, size_t count)
{
    size_t i = 0;
    int sigo = 1;

    if (fd < 0 || buf == NULL || count <= 0)
            return -1;

    if ( !FDM.fds[fd].open || FDM.fds[fd].getByte == NULL )
            return -1;

    while( i < count && sigo )
            sigo = FDM.fds[fd].getByte( (int *) &((unsigned char *)buf)[i++] );

    /* Si falla retorna -1*/
    if (!sigo)
        return 0;
    else
        return i;
}

void
__getVal(const char * src, void * dest)
{
    if(strcmp(src, "CurrentPID") == 0)
    {
        *((int *) dest) = CurrentPID;
    }
    return ;
}

void
__setVal(const char * dest, void * src)
{
    if(strcmp(dest, "CurrentPID") == 0)
    {
        CurrentPID = *((int *) src);
    }
    return ;
}

ssize_t
__write(int fd, const void  *buf, size_t count)
{

    size_t i = 0;
    int sigo = 1;

    if (fd < 0 || buf == NULL || count <= 0)
            return 0;

    if ( !FDM.fds[fd].open || FDM.fds[fd].setByte == NULL )
            return 0;

    while( i < count && sigo )
            sigo = FDM.fds[fd].setByte( (int) ((unsigned char *)buf)[i++] );

    return i;
}

static void
SetupPaging(void)
{
    // holds the physical address of where a page is
    unsigned long address=0;
    unsigned int i;

    for (i = 0 ; i < CANT_PAGES; i++)
    {
        page_pid[i] = -1;
    }
    // map the first 4MB of memory
    for(i=0; i<1024; i++)
    {
        // attribute set to: supervisor level, read/write, present(011 in binary)
        page_table[i] = address | 3;
        address = address + 4096; // 4096 = 4kb
    }

    for(i=0; i<1024; i++)
    {
        // Las paginas para el heap y el stack de los procesos estan todas
        // ausentes hasta que un proceso requiera alguna pagina para poder
        // operar.
        page_table2[i] = address & 0xFFFFFFFE;

        address = address + 4096; // 4096 = 4kb
    }

    // fill the first two entries of the page directory
    // attribute set to: supervisor level, read/write, present(011 in binary)
    page_directory[0] = (int) page_table;
    page_directory[0] = page_directory[0] | 3;
    // attribute set to: supervisor level, read/write, present(011 in binary)
    page_directory[1] = (int) page_table2;
    page_directory[1] = page_directory[1] | 3;

    /* All the other 1023 page tables are set to not present */
    for(i=2; i<1024; i++)
    {
        // attribute set to: supervisor level, read/write,
        // not present(010 in binary)
        page_directory[i] = 0 | 2;
    }

    // write_cr3, read_cr3, write_cr0, and read_cr0 all come from the assembly
    // functions

    // put that page directory address into CR3
    _write_cr3(page_directory);
    // set the paging bit in CR0 to 1
    _write_cr0((unsigned long *)(_read_cr0() | 0x80000000));

    return;
}

//Funcion que destruye un proceso a partir de su PID
void Destroy(int PID)
{
    PROCESS * proc, * padre;
    TTY end_tty;
    int i, j;

    proc=GetProcessByPID(PID);

    if(!proc->free)
    {
        if(proc->foreground)
        {
            end_tty = tty[proc->tty];
            for(i = 0; i < TTY_BUFFER_SIZE; i++)
                end_tty.buffer.buffer[i] = 0;
            end_tty.buffer.first = 0;
            end_tty.buffer.last = 0;
            end_tty.buffer.cant = 0;
        }

        if (proc->parent!=0)
        {
            padre=GetProcessByPID(proc->parent);
            if(padre->blocked == 2)
                padre->blocked=0;
        }

        for(j = 0; j < CANT_PAGES; j++)
        {
            if(page_pid[j] == proc->pid || page_pid[j] == -(proc->pid * 100))
            {
                page_pid[j] = -1;
                page_table2[j] = page_table2[j] & 0xFFFFFFFE;
            }
        }
        proc->free = 1;
    }
    else
        printf("Proceso Inexistente");

    return ;
}

